﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;

namespace MVCnExt.Web.UI.Ext
{
    public class FastProperty<T, P>
    {
        public PropertyInfo Property { get; set; }

        public Func<T, P> GetDelegate;
        public Action<T, P> SetDelegate;

        public FastProperty(PropertyInfo property)
        {
            this.Property = property;
            InitializeGet();
            InitializeSet();
        }

        private void InitializeSet()
        {
            var instance = Expression.Parameter(typeof(T), "instance");
            var value = Expression.Parameter(typeof(P), "value");
            this.SetDelegate = Expression.Lambda<Action<T, P>>(Expression.Call(instance, this.Property.GetSetMethod(), value), new ParameterExpression[] { instance, value }).Compile();

            // roughly looks like Action<T,P> a = new Action<T,P>((instance,value) => instance.set_Property(value));
        }

        private void InitializeGet()
        {
            var instance = Expression.Parameter(typeof(T), "instance");
            this.GetDelegate = Expression.Lambda<Func<T, P>>(Expression.Call(instance, this.Property.GetGetMethod()), instance).Compile();

            // roughly looks like Func<T,P> getter = instance => return instance.get_Property();
        }

        public P Get(T instance)
        {
            return this.GetDelegate(instance);
        }

        public void Set(T instance, P value)
        {
            this.SetDelegate(instance, value);
        }
    }

    public class FastProperty<T>
    {
        public PropertyInfo Property { get; set; }

        public Func<T, object> GetDelegate;
        public Action<T, object> SetDelegate;

        public FastProperty(PropertyInfo property)
        {
            this.Property = property;
            InitializeGet();
            InitializeSet();
        }

        private void InitializeSet()
        {
            var instance = Expression.Parameter(typeof(T), "instance");
            var value = Expression.Parameter(typeof(object), "value");
            UnaryExpression valueCast = (!this.Property.PropertyType.IsValueType) ? Expression.TypeAs(value, this.Property.PropertyType) : Expression.Convert(value, this.Property.PropertyType);
            this.SetDelegate = Expression.Lambda<Action<T, object>>(Expression.Call(instance, this.Property.GetSetMethod(), valueCast), new ParameterExpression[] { instance, value }).Compile();
        }

        private void InitializeGet()
        {
            var instance = Expression.Parameter(typeof(T), "instance");
            this.GetDelegate = Expression.Lambda<Func<T, object>>(Expression.TypeAs(Expression.Call(instance, this.Property.GetGetMethod()), typeof(object)), instance).Compile();
        }

        public object Get(T instance)
        {
            return this.GetDelegate(instance);
        }

        public void Set(T instance, object value)
        {
            this.SetDelegate(instance, value);
        }


    }

    public class FastProperty
    {
        public PropertyInfo Property { get; set; }

        public Func<object, object> GetDelegate;
        public Action<object, object> SetDelegate;

        public FastProperty(PropertyInfo property)
        {
            this.Property = property;
            InitializeGet();
            InitializeSet();
        }

        private void InitializeSet()
        {
            var instance = Expression.Parameter(typeof(object), "instance");
            var value = Expression.Parameter(typeof(object), "value");

            // value as T is slightly faster than (T)value, so if it's not a value type, use that
            UnaryExpression instanceCast = (!this.Property.DeclaringType.IsValueType) ? Expression.TypeAs(instance, this.Property.DeclaringType) : Expression.Convert(instance, this.Property.DeclaringType);
            UnaryExpression valueCast = (!this.Property.PropertyType.IsValueType) ? Expression.TypeAs(value, this.Property.PropertyType) : Expression.Convert(value, this.Property.PropertyType);
            this.SetDelegate = Expression.Lambda<Action<object, object>>(Expression.Call(instanceCast, this.Property.GetSetMethod(), valueCast), new ParameterExpression[] { instance, value }).Compile();
        }

        private void InitializeGet()
        {
            var instance = Expression.Parameter(typeof(object), "instance");
            UnaryExpression instanceCast = (!this.Property.DeclaringType.IsValueType) ? Expression.TypeAs(instance, this.Property.DeclaringType) : Expression.Convert(instance, this.Property.DeclaringType);
            this.GetDelegate = Expression.Lambda<Func<object, object>>(Expression.TypeAs(Expression.Call(instanceCast, this.Property.GetGetMethod()), typeof(object)), instance).Compile();
        }

        public object Get(object instance)
        {
            return this.GetDelegate(instance);
        }

        public void Set(object instance, object value)
        {
            this.SetDelegate(instance, value);
        }
    }

}
